package vip.liux.backend.application.serviceImpl.pay;

import jakarta.annotation.Resource;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.VisibleForTesting;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import vip.liux.application.dots.ListResultDto;
import vip.liux.application.dots.PagedResultDto;
import vip.liux.backend.application.service.pay.*;
import vip.liux.backend.application.service.pay.dto.app.PayAppDto;
import vip.liux.backend.application.service.pay.dto.channel.PayChannelDto;
import vip.liux.backend.application.service.pay.dto.order.*;
import vip.liux.backend.infrastructure.code.BizCodeGenerator;
import vip.liux.contracts.infrastructure.pay.PayClient;
import vip.liux.contracts.infrastructure.pay.dto.order.ClientPayOrderDto;
import vip.liux.contracts.infrastructure.pay.dto.order.PayOrderUnifiedDto;
import vip.liux.contracts.models.pay.PayOrder;
import vip.liux.contracts.repositories.pay.PayOrderRepository;
import vip.liux.contracts.shared.enums.pay.PayNotifyType;
import vip.liux.contracts.shared.enums.pay.PayOrderStatus;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static vip.liux.application.PageKit.toPage;
import static vip.liux.application.PageKit.toPaged;
import static vip.liux.backend.infrastructure.utils.ListJoinKit.backFill;
import static vip.liux.contracts.shared.enums.ErrorCode.*;
import static vip.liux.contracts.shared.util.ServiceExceptionUtil.exception;
import static vip.liux.core.utils.JsonUtils.toJson;


/**
 * 支付订单 Service 实现类
 *
 * @author aquan
 */
@Service
@Validated
@Slf4j
public class PayOrderServiceImpl implements PayOrderService, ApplicationContextAware {

    @Resource
    private PayOrderRepository payOrderRepo;

    @Resource
    private PayAppService appService;

    @Resource
    private PayChannelService channelService;

    @Resource
    private PayNotifyService notifyService;

    @Resource
    private BizCodeGenerator codeGenerator;

    @Override
    public PayOrderDto getOrder(Long id) {
        PayOrderDto order = AutoMapperProfile.INSTANCE.convert(payOrderRepo.findById(id).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND)));
        Optional.ofNullable(order)
                .ifPresent(o -> Optional.ofNullable(
                        appService.getApp(o.getAppId())).ifPresent(p -> o.setAppName(p.getName())
                ));
        return order;
    }

    @Override
    public PayOrderDto getByOrderNo(String orderNo) {
        PayOrderDto order = AutoMapperProfile.INSTANCE.convert(payOrderRepo.findByOrderNo(orderNo).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND)));
        Optional.ofNullable(order)
                .ifPresent(o -> Optional.ofNullable(
                        appService.getApp(o.getAppId())).ifPresent(p -> o.setAppName(p.getName())
                ));
        return order;
    }

    @Override
    public PayOrderDto getOrder(Long appId, String merchantOrderId) {
        PayOrderDto order = AutoMapperProfile.INSTANCE.convert(payOrderRepo.findByAppIdAndMerchantOrderId(appId, merchantOrderId).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND)));
        Optional.ofNullable(order)
                .ifPresent(o -> Optional.ofNullable(
                        appService.getApp(o.getAppId())).ifPresent(p -> o.setAppName(p.getName())
                ));
        return order;
    }

    @Override
    public PayOrderDetailsDto getOrderDetail(Long id) {
        PayOrderDetailsDto order = AutoMapperProfile.INSTANCE.convertDetail(payOrderRepo.findById(id).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND)));
        Optional.ofNullable(order)
                .ifPresent(o -> Optional.ofNullable(
                        appService.getApp(o.getAppId())).ifPresent(p -> o.setAppName(p.getName())
                ));
        return order;
    }

    @Override
    public ListResultDto<PayOrderDto> getOrderList(Collection<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return new ListResultDto<>();
        }
        return new ListResultDto<>(
                AutoMapperProfile.INSTANCE.convertList(
                        payOrderRepo.findByIdIn(ids)
                )
        );
    }

    @Override
    public Long getOrderCountByAppId(Long appId) {
        return payOrderRepo.countByAppId(appId);
    }

    @Override
    public PagedResultDto<PayOrderDto> getOrderPage(PayOrderQueryDto input) {
        PagedResultDto<PayOrderDto> pagedResult = toPaged(payOrderRepo.findPageBy(
                input.getAppId(),
                input.getChannelCode(),
                input.getMerchantOrderId(),
                input.getOrderNo(),
                input.getChannelOrderNo(),
                input.getStatus(),
                input.getStart(),
                input.getEnd(),
                toPage(input)
        ).map(AutoMapperProfile.INSTANCE::convert));
        if (pagedResult.getTotalCount() > 0) {
            // 补充应用信息
            ListResultDto<PayAppDto> appList = appService.getAppList(pagedResult.getItems().stream().map(PayOrderDto::getAppId).toList());

            backFill(
                    pagedResult.getItems(),
                    appList.getItems(),
                    PayOrderDto::getAppId,
                    PayAppDto::getId,
                    (order, app) -> order.setAppName(app.getName())
            );
        }
        return pagedResult;
    }

    @Override
    public ListResultDto<PayOrderDto> getOrderList(PayOrderExportDto input) {
        return new ListResultDto<>(AutoMapperProfile.INSTANCE.convertList(
                payOrderRepo.findListBy(
                        input.getAppId(),
                        input.getChannelCode(),
                        input.getMerchantOrderId(),
                        input.getOrderNo(),
                        input.getChannelOrderNo(),
                        input.getStatus(),
                        input.getStart(),
                        input.getEnd()
                )
        ));
    }


    @Override
    public String createOrder(PayOrderCreateDto input) {
        // 校验 App
        PayAppDto app = appService.validPayApp(input.getAppKey());

        // 查询对应的支付交易单是否已经存在。如果是，则直接返回
        Optional<PayOrder> optional = payOrderRepo.findByAppIdAndMerchantOrderId(
                app.getId(), input.getMerchantOrderId());
        if (optional.isPresent()) {
            PayOrder order = optional.get();
            log.warn("[createOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
                    order.getMerchantOrderId(), toJson(order)); // 理论来说，不会出现这个情况
            return order.getOrderNo();
        }

        // 生产支付订单号
        String no = codeGenerator.generatePayNo();

        // 创建支付交易单
        PayOrder order = AutoMapperProfile.INSTANCE.convert(input)
                .setAppId(app.getId())
                .setOrderNo(no)
                // 商户相关字段
                .setNotifyUrl(app.getOrderNotifyUrl())
                // 订单相关字段
                .setStatus(PayOrderStatus.WAITING)
                // 退款相关字段
                .setRefundPrice(BigDecimal.ZERO);
        payOrderRepo.save(order);
        return order.getOrderNo();
    }

    private ApplicationContext applicationContext;

    @NotNull
    private PayOrder validateOrderCanSubmit(String orderNo) {
        PayOrder order = payOrderRepo.findByOrderNo(orderNo).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND));

        if (PayOrderStatus.isSuccess(order.getStatus())) { // 校验状态，发现已支付
            throw exception(PAY_ORDER_STATUS_IS_SUCCESS);
        }
        if (!PayOrderStatus.WAITING.equals(order.getStatus())) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        if (order.getExpireTime().isBefore(LocalDateTime.now())) { // 校验是否过期
            throw exception(PAY_ORDER_IS_EXPIRED);
        }

        // 【重要】校验是否支付订单已支付，只是没有回调、或者数据不正常
        validateOrderActuallyPaid(order);
        return order;
    }

    /**
     * 校验支付订单实际已支付
     *
     * @param order 支付订单
     */
    @VisibleForTesting
    void validateOrderActuallyPaid(PayOrder order) {
        // 情况一：校验数据库中的 order 是不是已支付
        if (PayOrderStatus.isSuccess(order.getStatus())) {
            log.warn("[validateOrderCanSubmit][merchantOrderId({}) 已支付，可能是数据不一致]", order.getMerchantOrderId());
            throw exception(PAY_ORDER_EXTENSION_IS_PAID);
        }
        if (Objects.isNull(order.getChannelId())) {
            return;
        }
        // 情况二：调用三方接口，查询支付单状态，是不是已支付
        PayClient<?> payClient = channelService.getPayClient(order.getChannelId());
        if (payClient == null) {
            log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", order.getChannelId());
            return;
        }
        ClientPayOrderDto clientOrderDto = payClient.getOrder(order.getOrderNo());
        if (clientOrderDto != null && PayOrderStatus.isSuccess(clientOrderDto.getStatus())) {
            log.warn("[validateOrderCanSubmit][order({}) 的 PayOrderRespDTO({}) 已支付，可能是回调延迟]", order.getId(), toJson(clientOrderDto));
            throw exception(PAY_ORDER_EXTENSION_IS_PAID);
        }
    }

    private PayChannelDto validateChannelCanSubmit(Long appId, String channelCode) {
        // 校验 App
        PayAppDto app = appService.validPayApp(appId);
        // 校验支付渠道是否有效
        PayChannelDto channel = channelService.validPayChannel(appId, channelCode);
        PayClient<?> client = channelService.getPayClient(channel.getId());
        if (client == null) {
            log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
            throw exception(CHANNEL_NOT_FOUND);
        }
        return channel;
    }

    /**
     * 根据支付渠道的编码，生成支付渠道的回调地址
     *
     * @param channel 支付渠道
     * @return 支付渠道的回调地址  配置地址 + "/" + channel id
     */
    private String genChannelOrderNotifyUrl(PayChannelDto channel) {
        // todo 构建回调地址
        return "https://backend.liux.vip" + "/" + channel.getId();
    }

    @Override
    public void notifyOrder(Long channelId, ClientPayOrderDto notify) {
        // 校验支付渠道是否有效
        PayChannelDto channel = channelService.validPayChannel(channelId);
        // 更新支付订单为已支付
        getSelf().notifyOrder(channel, notify);
    }

    /**
     * 通知并更新订单的支付结果
     *
     * @param channel 支付渠道
     * @param notify  通知
     */
    @Transactional(rollbackFor = Exception.class)
    // 注意，如果是方法内调用该方法，需要通过 getSelf().notifyPayOrder(channel, notify) 调用，否则事务不生效
    public void notifyOrder(PayChannelDto channel, ClientPayOrderDto notify) {
        // 情况一：支付成功的回调
        if (PayOrderStatus.isSuccess(notify.getStatus())) {
            notifyOrderSuccess(channel, notify);
            return;
        }
        // 情况二：支付失败的回调
        if (PayOrderStatus.isClosed(notify.getStatus())) {
            notifyOrderClosed(channel, notify);
        }
        // 情况三：WAITING：无需处理
        // 情况四：REFUND：通过退款回调处理
    }

    private void notifyOrderSuccess(PayChannelDto channel, ClientPayOrderDto notify) {
        // 2. 更新 PayOrderDO 支付成功
        Boolean paid = updateOrderSuccess(channel, notify);
        if (paid) { // 如果之前已经成功回调，则直接返回，不用重复记录支付通知记录；例如说：支付平台重复回调
            return;
        }
        PayOrder order = payOrderRepo.findByOrderNo(notify.getOutTradeNo()).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND));

        // 3. 插入支付通知记录
        notifyService.createPayNotifyTask(PayNotifyType.ORDER, order.getOrderNo());
    }

    /**
     * 更新 PayOrderDO 支付成功
     *
     * @param channel 支付渠道
     * @param notify  通知回调
     * @return 是否之前已经成功回调
     */
    private Boolean updateOrderSuccess(PayChannelDto channel, ClientPayOrderDto notify) {
        // 1. 判断 PayOrderDO 是否处于待支付
        PayOrder order = payOrderRepo.findByOrderNo(notify.getOutTradeNo()).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND));
        // 如果已经是成功，直接返回，不用重复更新
        if (PayOrderStatus.isSuccess(order.getStatus())) {
            log.info("[updateOrderSuccess][order({}) 已经是已支付，无需更新]", order.getId());
            return true;
        }
        if (!PayOrderStatus.WAITING.equals(order.getStatus())) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
        }

        // 2. 更新 PayOrderDO
        int updateCounts = payOrderRepo.updateOrderSuccess(
                order.getId(),
                order.getOrderNo(),
                PayOrderStatus.WAITING,
                PayOrderStatus.SUCCESS,
                channel.getId(),
                channel.getCode(),
                notify.getSuccessTime(),
                notify.getChannelOrderNo(),
                notify.getChannelUserId(),
                channel.getFeeRate(),
                order.getPrice().multiply(BigDecimal.valueOf(channel.getFeeRate())).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP),
                toJson(notify)
        );
        if (updateCounts == 0) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        log.info("[updateOrderExtensionSuccess][order({}) 更新为已支付]", order.getId());
        return false;
    }

    private void notifyOrderClosed(PayChannelDto channel, ClientPayOrderDto notify) {
        updateOrderClosed(channel, notify);
    }

    @Override // 注意，这里不能添加事务注解，避免调用支付渠道失败时，将 PayOrderExtension 回滚了
    public PayOrderSubmitDto submitOrder(PayOrderSubmitInputDto input, String userIp) {
        // 1.1 获得 PayOrderDO ，并校验其是否存在
        PayOrder order = validateOrderCanSubmit(input.getOrderNo());
        // 1.2 校验支付渠道是否有效
        PayChannelDto channel = validateChannelCanSubmit(order.getAppId(), input.getChannelCode());
        PayClient<?> client = channelService.getPayClient(channel.getId());

        // 2. 调用三方接口
        PayOrderUnifiedDto unifiedOrderDto = AutoMapperProfile.INSTANCE.convert2(input, userIp)
                // 商户相关的字段
                .setOutTradeNo(order.getOrderNo()) // 注意，此处使用的是 PayOrderExtension.no 属性！
                .setSubject(order.getSubject()).setBody(order.getBody())
                .setNotifyUrl(genChannelOrderNotifyUrl(channel))
                .setReturnUrl(input.getReturnUrl())
                // 订单相关字段
                .setPrice(order.getPrice())
                .setExpireTime(order.getExpireTime());
        ClientPayOrderDto unifiedOrderResp = client.unifiedOrder(unifiedOrderDto);

        // 3. 如果调用直接支付成功，则直接更新支付单状态为成功。例如说：付款码支付，免密支付时，就直接验证支付成功
        if (unifiedOrderResp != null) {
            try {
                getSelf().notifyOrder(channel, unifiedOrderResp);
            } catch (Exception e) {
                // 兼容 https://gitee.com/zhijiantianya/yudao-cloud/issues/I8SM9H 场景
                // 支付宝或微信扫码之后时，由于 PayClient 是直接返回支付成功，而支付也会有回调，导致存在并发更新问题，此时一般是可以 try catch 直接忽略
                log.warn("[submitOrder][order({}) channel({}) 支付结果({}) 通知时发生异常，可能是并发问题]",
                        order, channel, unifiedOrderResp, e);
            }
            // 如有渠道错误码，则抛出业务异常，提示用户
            if (StringUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) {
                throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
                        unifiedOrderResp.getChannelErrorMsg());
            }
            // 此处需要读取最新的状态
            order = payOrderRepo.findById(Objects.requireNonNull(order.getId())).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND));
        }
        return AutoMapperProfile.INSTANCE.convert(order, unifiedOrderResp);
    }

    @Override
    public void updateOrderRefundPrice(String orderNo, BigDecimal incrRefundPrice) {
        PayOrder order = payOrderRepo.findByOrderNo(orderNo).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND));

        if (!PayOrderStatus.isSuccessOrRefund(order.getStatus())) {
            throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
        }
        if (order.getRefundPrice().add(incrRefundPrice).compareTo(order.getPrice()) > 0) {
            throw exception(REFUND_PRICE_EXCEED);
        }

        // 更新订单
        int updateCount = payOrderRepo.updateByIdAndStatus(orderNo, order.getStatus(), order.getRefundPrice().add(incrRefundPrice));
        if (updateCount == 0) {
            throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
        }
    }

    @SuppressWarnings("unused")
    private void updateOrderClosed(PayChannelDto channel, ClientPayOrderDto notify) {
        // 1. 查询 PayOrder
        PayOrder order = payOrderRepo.findByOrderNo(notify.getOutTradeNo()).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND));

        if (PayOrderStatus.isClosed(order.getStatus())) { // 如果已经是关闭，直接返回，不用重复更新
            log.info("[updateOrderClosed][order({}) 已经是支付关闭，无需更新]", order.getId());
            return;
        }
        // 一般出现先是支付成功，然后支付关闭，都是全部退款导致关闭的场景。这个情况，我们不更新支付订单，只通过退款流程，更新支付单
        if (PayOrderStatus.isSuccess(order.getStatus())) {
            log.info("[updateOrderClosed][order({}) 是已支付，无需更新为支付关闭]", order.getId());
            return;
        }
        if (ObjectUtils.notEqual(order.getStatus(), PayOrderStatus.WAITING)) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
        }

        // 2. 更新 PayOrderExtension
        int updateCounts = payOrderRepo.updateOrderClosed(
                order.getId(),
                order.getOrderNo(),
                channel.getId(),
                channel.getCode(),
                order.getStatus(),
                PayOrderStatus.CLOSED,
                toJson(notify),
                notify.getChannelErrorCode(),
                notify.getChannelErrorMsg());
        if (updateCounts == 0) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
        }
        log.info("[updateOrderClosed][order({}) 更新为支付关闭]", order.getId());
    }

    @Override
    public int syncOrder(LocalDateTime minCreationTime) {
        // 1. 查询指定创建时间前的待支付订单
        List<PayOrder> orders = payOrderRepo.findByStatusAndCreationTimeGreaterThan(PayOrderStatus.WAITING, minCreationTime);
        if (CollectionUtils.isEmpty(orders)) {
            return 0;
        }
        // 2. 遍历执行
        int count = 0;
        for (PayOrder order : orders) {
            count += syncOrder(order) ? 1 : 0;
        }
        return count;
    }

    @Override
    public void syncOrderQuietly(Long id) {
        // 1. 查询待支付订单
        List<PayOrder> orders = payOrderRepo.findByIdAndStatus(id, PayOrderStatus.WAITING);

        // 2. 遍历执行
        for (PayOrder order : orders) {
            syncOrder(order);
        }
    }

    @Override
    public int expireOrder() {
        // 1. 查询过期的待支付订单
        List<PayOrder> orders = payOrderRepo.selectListByStatusAndExpireTimeLt(
                PayOrderStatus.WAITING, LocalDateTime.now()
        );
        if (CollectionUtils.isEmpty(orders)) {
            return 0;
        }

        // 2. 遍历执行
        int count = 0;
        for (PayOrder order : orders) {
            count += expireOrder(order) ? 1 : 0;
        }
        return count;
    }

    /**
     * 同步单个支付订单
     *
     * @param orderExtension 支付订单
     * @return 是否已支付
     */
    private boolean syncOrder(PayOrder orderExtension) {
        try {
            // 1.1 查询支付订单信息
            PayClient<?> payClient = channelService.getPayClient(orderExtension.getChannelId());
            if (payClient == null) {
                log.error("[syncOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId());
                return false;
            }
            ClientPayOrderDto respDTO = payClient.getOrder(orderExtension.getOrderNo());
            // 如果查询到订单不存在，PayClient 返回的状态为关闭。但此时不能关闭订单。存在以下一种场景：
            //  拉起渠道支付后，短时间内用户未及时完成支付，但是该订单同步定时任务恰巧自动触发了，主动查询结果为订单不存在。
            //  当用户支付成功之后，该订单状态在渠道的回调中无法从已关闭改为已支付，造成重大影响。
            // 考虑此定时任务是异常场景的兜底操作，因此这里不做变更，优先以回调为准。
            // 让订单自动随着支付渠道那边一起等到过期，确保渠道先过期关闭支付入口，而后通过订单过期定时任务关闭自己的订单。
            if (PayOrderStatus.isClosed(respDTO.getStatus())) {
                return false;
            }
            // 1.2 回调支付结果
            notifyOrder(orderExtension.getChannelId(), respDTO);

            // 2. 如果是已支付，则返回 true
            return PayOrderStatus.isSuccess(respDTO.getStatus());
        } catch (Throwable e) {
            log.error("[syncOrder][orderExtension({}) 同步支付状态异常]", orderExtension.getId(), e);
            return false;
        }
    }

    /**
     * 同步单个支付单
     *
     * @param order 支付单
     * @return 是否已过期
     */
    private boolean expireOrder(PayOrder order) {
        try {
            // 1. 需要先处理关联的支付订单，避免错误的过期已支付 or 已退款的订单
            if (PayOrderStatus.isClosed(order.getStatus())) {
                return false;
            }
            // 情况一：校验数据库中的 orderExtension 是不是已支付
            if (PayOrderStatus.isSuccess(order.getStatus())) {
                log.error("[expireOrder][order({})) 已支付，可能是数据不一致]", order.getId());
                return false;
            }

            // 如果没有渠道编号，则说明是未支付的订单，直接返回
            if (Objects.isNull(order.getChannelId())) {
                return false;
            }
            // 情况二：调用三方接口，查询支付单状态，是不是已支付/已退款
            PayClient<?> payClient = channelService.getPayClient(order.getChannelId());
            if (payClient == null) {
                log.error("[expireOrder][渠道编号({}) 找不到对应的支付客户端]", order.getChannelId());
                return false;
            }
            ClientPayOrderDto respDTO = payClient.getOrder(order.getOrderNo());
            if (PayOrderStatus.isRefund(respDTO.getStatus())) {
                // 补充说明：按道理，应该是 WAITING => SUCCESS => REFUND 状态，如果直接 WAITING => REFUND 状态，说明中间丢了过程
                // 此时，需要人工介入，手工补齐数据，保持 WAITING => SUCCESS => REFUND 的过程
                log.error("[expireOrder][order({}) 的 ClientPayOrderDto({}) 已退款，可能是回调延迟]", order.getId(), toJson(respDTO));
                return false;
            }
            if (PayOrderStatus.isSuccess(respDTO.getStatus())) {
                notifyOrder(order.getChannelId(), respDTO);
                return false;
            }
            // 兜底逻辑：将支付订单更新为已关闭
            if (payOrderRepo.updateOrderClosed(order.getId(), order.getOrderNo(), order.getChannelId(), order.getChannelCode(), PayOrderStatus.WAITING, PayOrderStatus.CLOSED, toJson(respDTO), null, null) == 0) {
                log.error("[expireOrder][extension({}) 更新为支付关闭失败]", order.getId());
                return false;
            }
            log.info("[expireOrder][order({}) 更新为支付关闭成功]", order.getId());
            return true;
        } catch (Throwable e) {
            log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e);
            return false;
        }
    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PayOrderServiceImpl getSelf() {
        return applicationContext.getBean(getClass());
    }

    @Override
    public void updatePayOrderPrice(String orderNo, BigDecimal payPrice) {
        PayOrder order = payOrderRepo.findByOrderNo(orderNo).orElseThrow(() -> exception(PAY_ORDER_NOT_FOUND));

        if (ObjectUtils.notEqual(PayOrderStatus.WAITING, order.getStatus())) {
            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        if (Objects.equals(order.getPrice(), payPrice)) {
            return;
        }

        payOrderRepo.updateByIdSetPrice(order.getId(), payPrice);
    }

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
